#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"



struct {
  struct spinlock lock;

  struct buf buf[NBUF];

  struct buf head;
} bcache;

void bprint(void) {
  struct buf * p = &(bcache.head); 
  do {
#ifdef DEBUG
    printf("data:%d ", *(p->data));
#endif
    buf_print(p);
    p = p->next;
  } while(p != &(bcache.head));
}


void binit(void) {
  initlock(&bcache.lock, "bcache");
  
  buf_init(&bcache.head, &bcache.head, &bcache.head);
#ifdef DEBUG
  *(bcache.head.data) = -1;
#endif
  
  struct buf * p;
  for(struct buf * p = bcache.buf ; p < bcache.buf + NBUF; p++){
    buf_init(p, &bcache.head, bcache.head.next);

    bcache.head.next = p;
    p->next->prev = p;
#ifdef DEBUG
    *(p->data) = p - bcache.buf;
#endif
  }

#ifdef DEBUG
  bprint();
#endif
}


static struct buf * bget(uint dev, uint blockno){
  acquire(&bcache.lock);
  
  //bprint(); 
  for(struct buf * p = bcache.head.next; p != &bcache.head; p = p->next){
    if(p->dev == dev && p->blockno == blockno){
      p->refcnt++;
      release(&bcache.lock);
      acquiresleep(&p->lock);
      return p;
    }
  }
  
  //not found 
  for(struct buf * p = bcache.head.next; p != &bcache.head; p = p->next){
    if(p->refcnt == 0){
      p->dev = dev;
      p->blockno = blockno;

      p->valid = 0;
      p->refcnt = 1;
      release(&bcache.lock);
      acquiresleep(&p->lock);
      return p;
    }
  }
  
  panic("bget no buffers");
}

//TODO: unittes
struct buf * bread(uint dev, uint blockno) {
  struct buf * p = bget(dev, blockno);
  if(!p->valid) {
    virtio_disk_read(p);
    p->valid = 1;
  }
  return p;
}

//TODO: unittes
void bwrite(struct buf * b) {
  if(!holdingsleep(&b->lock))
    panic("bwrite");
  virtio_disk_write(b);
}

void brelse(struct buf * b) {
  if(!holdingsleep(&b->lock))
    panic("brelse");
  
  releasesleep(&b->lock);

  acquire(&bcache.lock);
  b->refcnt --;
  if(b->refcnt == 0){
    b->prev->next = b->next;
    b->next->prev = b->prev;

    b->next = bcache.head.next;
    b->prev = &bcache.head;

    //bcache.head.next->prev = b;
    //bcache.head.next = b;

    b->next->prev = b;
    b->prev->next = b;
  }

  release(&bcache.lock);
}



void bpin(struct buf * b){
  acquire(&bcache.lock);
  b->refcnt ++;
  release(&bcache.lock);
}
void bunpin(struct buf * b){
  acquire(&bcache.lock);
  b->refcnt --;
  release(&bcache.lock);
}

int t__block_in_bcache(uint dev, uint blockno){
  acquire(&bcache.lock);
  for(struct buf * p = bcache.head.next; p != &bcache.head; p = p->next){
    if(p->dev == dev && p->blockno == blockno){
      release(&bcache.lock);
      return 1;
    }
  }
  release(&bcache.lock);
  return 0;
}
